package org.zoomdy.gdbx.os;

import java.io.IOException;
import java.util.HashMap;

import org.zoomdy.gdbx.Debugger;
import org.zoomdy.gdbx.OSNode;
import org.zoomdy.gdbx.arch.Register;
import org.zoomdy.gdbx.arch.TargetRegisters;
import org.zoomdy.gdbx.arch.TargetRegistersCortexM;
import org.zoomdy.gdbx.arch.TargetRegistersI386;

public class eCos extends RTOS
{
    long addrThreadListHead;
    long addrCurrentThread;
    long offsetStackPointer;
    long offsetNextPointer;
    long offsetNamePointer;
    long offsetState;
    long offsetId;
    long offsetPriority;
    
    HashMap<Integer, ThreadInfo> threads;
    int currentThreadId;
    int selectThreadId;
    
    ThreadContextType threadContextType;
    ThreadContext  threadContext;
    TargetRegisters targetRegisters;
    
    public eCos(Debugger debugger, OSNode node)
    {
        super(debugger, node);
        
        this.threadContextType = ThreadContextType.NONE;
    }
    
    public boolean detect()
    {
        Long result = this.debugger.evaluateExpression("&Cyg_Thread::thread_list");
        if(result == null)
        {
            return false;
        }
        this.addrThreadListHead = result;
        this.debug(1, "&Cyg_Thread::thread_list=0x" + Long.toHexString(this.addrThreadListHead));
        
        result = this.debugger.evaluateExpression("&Cyg_Scheduler_Base::current_thread");
        if(result == null)
        {
            return false;
        }
        this.addrCurrentThread = result;
        this.debug(1, "&Cyg_Scheduler_Base::current_thread=0x" + Long.toHexString(this.addrCurrentThread));
        
        result = this.debugger.evaluateExpression("&idle_thread");
        if(result == null)
        {
            return false;
        }
        
        Long base = result;
        this.debug(2, "&idle_thread=0x" + Long.toHexString(base));
        
        result = this.debugger.evaluateExpression("&idle_thread->stack_ptr");
        if(result == null)
        {
            return false;
        }
        this.offsetStackPointer = result - base;
        this.debug(2, "&idle_thread->stack_ptr=" + this.offsetStackPointer);
        
        result = this.debugger.evaluateExpression("&idle_thread->list_next");
        if(result == null)
        {
            return false;
        }
        this.offsetNextPointer = result - base;
        this.debug(2, "&idle_thread->list_next=" + this.offsetNextPointer);
        
        result = this.debugger.evaluateExpression("&idle_thread->name");
        if(result == null)
        {
            this.offsetNamePointer = -1;
        }
        else
        {
            this.offsetNamePointer = result - base;
        }
        this.debug(2, "&idle_thread->name=" + this.offsetNamePointer);
        
        result = this.debugger.evaluateExpression("&idle_thread->state");
        if(result == null)
        {
            return false;
        }
        this.offsetState = result - base;
        this.debug(2, "&idle_thread->state=" + this.offsetState);
        
        result = this.debugger.evaluateExpression("&idle_thread->unique_id");
        if(result == null)
        {
            return false;
        }
        this.offsetId = result - base;
        this.debug(2, "&idle_thread->unique_id=" + this.offsetId);
        
        result = this.debugger.evaluateExpression("&idle_thread->priority");
        if(result == null)
        {
            this.offsetPriority = -1;
        }
        else
        {
            this.offsetPriority = result - base;
            this.debug(2, "&idle_thread->priority=" + this.offsetPriority);
        }
        
        // 通过线程上下文数据结构成员变量名可以判断出这是什么架构
        String expr = String.format("(((HAL_SavedRegisters*)%d).eax)", base);
        result = this.debugger.evaluateExpression(expr);
        if(result != null)
        {
            this.threadContextType = ThreadContextType.I386;
            this.debug(2, "i386 architecture detected");
        }
        
        if(this.threadContextType == ThreadContextType.NONE)
        {
            expr = String.format("(((HAL_SavedRegisters*)%d).u.thread.basepri)", base);
            result = this.debugger.evaluateExpression(expr);
            if(result != null)
            {
                this.threadContextType = ThreadContextType.CORTEXM;
                this.debug(2, "cortexm architecture detected");
            }
        }
        
        if(this.threadContextType == ThreadContextType.I386)
        {
            // 通过线程上下文数据结构成员变量的位置可以判断出其它细节，例如是否支持FPU等
            expr = String.format("&(((HAL_SavedRegisters*)%d).edi)", base);
            result = this.debugger.evaluateExpression(expr);
            assert result != null;
            
            if(result.longValue() == base)
            {
                // 不包含浮点数支持
            }
            else if(result.longValue() == base + 4)
            {
                // 浮点上下文是个指针，为CYGHWR_HAL_I386_FPU_SWITCH_LAZY配置
                this.threadContextType = ThreadContextType.I386_FPU_LAZY;
                this.debug(2, "i386 with fpu switch lazy");
            }
            else
            {
                // 浮点上下文是个结构体，为CYGHWR_HAL_I386_FPU配置
                this.threadContextType = ThreadContextType.I386_FPU;
                this.debug(2, "i386 with fpu");
            }
            
            this.threadContext = new ThreadContextI386(this.threadContextType);
            this.targetRegisters = new TargetRegistersI386();
        }
        
        if(this.threadContextType == ThreadContextType.CORTEXM)
        {
            // 通过线程上下文数据结构成员变量的位置可以判断出其它细节，例如是否支持FPU等
            expr = String.format("&(((HAL_SavedRegisters*)%d).u.thread.pc)", base);
            result = this.debugger.evaluateExpression(expr);
            assert result != null;
            
            if(result.longValue() == base + 64)
            {
                // 不包含浮点数支持
            }
            else
            {
                // 包含浮点数上下文
                this.threadContextType = ThreadContextType.CORTEXM_FPU;
                this.debug(2, "cortexm with fpu");
            }
            
            this.threadContext = new ThreadContextCortexM(this.threadContextType);
            this.targetRegisters = new TargetRegistersCortexM();
        }
        
        return true;
    }
    
    void updateThreadList() throws IOException
    {
        this.threads = new HashMap<Integer, ThreadInfo>();
        
        long head = this.node.readInt(this.addrThreadListHead);
        long next = head;
        do
        {
            ThreadInfo thread = new ThreadInfo();
            if(this.offsetNamePointer == -1)
            {
                thread.namePointer = -1;
            }
            else
            {
                thread.namePointer = this.node.readInt(next + this.offsetNamePointer);
            }
            thread.stackPointer = this.node.readInt(next + this.offsetStackPointer);
            thread.id = this.node.readShort(next + this.offsetId);
            thread.state = this.node.readInt(next + this.offsetState);
            if(thread.namePointer > 0)
            {
                thread.name = this.node.readString(thread.namePointer, 64);
            }
            else
            {
                thread.name = "***NoName***";
            }
            
            if(this.offsetPriority != -1)
            {
                thread.priority = this.node.readInt(next + this.offsetPriority);
            }
            else
            {
                thread.priority = -1;
            }
            
            this.threads.put(thread.id, thread);
            
            next = this.node.readInt(next + this.offsetNextPointer);
        }while(next != head);
        
        long cur = this.node.readInt(this.addrCurrentThread);
        this.currentThreadId = this.node.readShort(cur + this.offsetId);
        
        this.needUpdate = false;
    }
    
    ThreadState parseThreadState(long state)
    {
        if((state & ThreadState.SLEEPING.mask) > 0)
        {
            return ThreadState.SLEEPING;
        }
        
        if((state & ThreadState.COUNTSLEEP.mask) > 0)
        {
            return ThreadState.COUNTSLEEP;
        }
        
        if((state & ThreadState.SUSPENDED.mask) > 0)
        {
            return ThreadState.SUSPENDED;
        }
        
        if((state & ThreadState.CREATING.mask) > 0)
        {
            return ThreadState.CREATING;
        }
        
        if((state & ThreadState.EXITED.mask) > 0)
        {
            return ThreadState.EXITED;
        }
        
        return ThreadState.RUNNING;
    }
    
    public String getThreadList(boolean first) throws IOException
    {
        if(this.needUpdate)
        {
            this.updateThreadList();
        }
        
        String threadList = "m";
        if(first)
        {
            if(this.threads != null && !this.threads.isEmpty())
            {
                for(Integer id: this.threads.keySet())
                {
                    threadList += Integer.toHexString(id) + ",";
                }
                threadList = threadList.substring(0, threadList.length() - 1);
            }
        }
        else
        {
            threadList = "l";
        }
        
        return threadList;
    }
    
    public String getThreadInfo(int id) throws IOException
    {
        if(this.needUpdate)
        {
            this.updateThreadList();
        }
        
        if(this.threads != null && this.threads.containsKey(id))
        {
            ThreadInfo thread = this.threads.get(id);
            String strState;
            if((this.parseThreadState(thread.state) == ThreadState.RUNNING)
                    && (id != this.currentThreadId))
            {
                strState = "READY";
            }
            else
            {
                strState = this.parseThreadState(thread.state).toString();
            }
            
             String threadInfo = String.format("state:%s;prio:%d;name:\"%s\"", 
                        strState, thread.priority, thread.name);
            
            return threadInfo;
        }
        else
        {
            return null;
        }
    }
    
    public boolean setCurrentThread(String op, int id) throws IOException
    {
        this.selectThreadId = id;
        
        if(id == eCos.THREAD_ID_ANY || id == eCos.THREAD_ID_ALL)
        {
            return true;
        }
        else
        {
            if(this.needUpdate)
            {
                this.updateThreadList();
            }
            
            if(this.threads != null && this.threads.containsKey(id))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    
    public int getCurrentThread() throws IOException
    {
        if(this.needUpdate)
        {
            this.updateThreadList();
        }
        
        if(this.threads != null)
        {
            return this.currentThreadId;
        }
        else
        {
            return 0;
        }
    }
    
    public void targetRunning()
    {
        super.targetRunning();
        this.selectThreadId = eCos.THREAD_ID_ANY;
    }
    
    public boolean isAliveThread(int id) throws IOException
    {
        if(this.needUpdate)
        {
            this.updateThreadList();
        }
        
        if(this.threads != null && this.threads.containsKey(id))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    String readThreadRegisters() throws IOException
    {
        int id = this.selectThreadId;
        
        if(this.threads != null && this.threads.containsKey(id))
        {
            ThreadInfo thread = this.threads.get(id);
            long stack = thread.stackPointer;
            stack += this.threadContext.offset;
            String registers = this.node.readRegisters();
            String context = this.node.readMemory(stack, this.threadContext.length);
            for(Register reg : this.targetRegisters.arrayRegisters)
            {
                Register threadReg = this.threadContext.getRegister(reg.name);
                threadReg = this.threadContext.getRegister(reg.name);
                
                if(threadReg != null)
                {
                    int offset = threadReg.offset;
                    registers = registers.substring(0, reg.offset * 2) + 
                            context.substring(offset * 2, offset * 2 + reg.length * 2) +
                            registers.substring(reg.offset * 2 + reg.length * 2);
                }
            }
            
            return registers;
        }
        else
        {
            return null;
        }
    }
    
    public String readRegisters() throws IOException
    {
        this.debug(3, "this.selectThreadId=" + this.selectThreadId);
        
        if(this.selectThreadId == eCos.THREAD_ID_ANY || 
                this.selectThreadId == eCos.THREAD_ID_ALL)
        {
            return null;
        }
        
        if(this.needUpdate)
        {
            this.updateThreadList();
        }
        
        if(this.selectThreadId == this.currentThreadId)
        {
            return null;
        }
        
        if(this.targetRegisters != null && this.threadContext != null)
        {
            return this.readThreadRegisters();
        }
        else
        {
            return null;
        }
    }
    
    class ThreadContextI386 extends ThreadContext
    {
        ThreadContextI386(ThreadContextType threadContextType)
        {
            super(threadContextType);
            
            int numRegisters = 0;
            if(this.threadContextType == ThreadContextType.I386)
            {
                this.offset = 0;
                numRegisters = 12;
            }
            else if(this.threadContextType == ThreadContextType.I386_FPU_LAZY)
            {
                this.offset = 4;
                numRegisters = 12;
            }
            else
            {
                assert false;
            }
            
            this.length = numRegisters * 4;
            this.arrayRegisters = new Register[numRegisters];
            
            this.arrayRegisters[0] = new Register("edi", 0, 4);
            this.arrayRegisters[1] = new Register("esi", 4, 4);
            this.arrayRegisters[2] = new Register("ebp", 8, 4);
            this.arrayRegisters[3] = new Register("esp", 12, 4);
            this.arrayRegisters[4] = new Register("ebx", 16, 4);
            this.arrayRegisters[5] = new Register("edx", 20, 4);
            this.arrayRegisters[6] = new Register("ecx", 24, 4);
            this.arrayRegisters[7] = new Register("eax", 28, 4);
            this.arrayRegisters[8] = new Register("__vector", 32, 4);
            this.arrayRegisters[9] = new Register("eip", 36, 4); 
            this.arrayRegisters[10] = new Register("cs", 40, 4);
            this.arrayRegisters[11] = new Register("eflags", 44, 4);
        }
    }
    
    class ThreadContextCortexM extends ThreadContext
    {
        ThreadContextCortexM(ThreadContextType threadContextType)
        {
            super(threadContextType);
            
            int numRegisters = 0;
            if(this.threadContextType == ThreadContextType.CORTEXM)
            {
                this.offset = 4;
                numRegisters = 16;
            }
            else
            {
                assert false;
            }
            
            this.length = numRegisters * 4;
            this.arrayRegisters = new Register[numRegisters];
            
            this.arrayRegisters[0] = new Register("basepri", 0, 4);
            this.arrayRegisters[1] = new Register("sp", 4, 4);
            
            this.arrayRegisters[2] = new Register("r0", 8, 4);
            this.arrayRegisters[3] = new Register("r1", 12, 4);
            this.arrayRegisters[4] = new Register("r2", 16, 4);
            this.arrayRegisters[5] = new Register("r3", 20, 4);
            this.arrayRegisters[6] = new Register("r4", 24, 4);
            this.arrayRegisters[7] = new Register("r5", 28, 4);
            this.arrayRegisters[8] = new Register("r6", 32, 4);
            this.arrayRegisters[9] = new Register("r7", 36, 4); 
            
            this.arrayRegisters[10] = new Register("r8", 40, 4);
            this.arrayRegisters[11] = new Register("r9", 44, 4);
            this.arrayRegisters[12] = new Register("r10", 48, 4);
            this.arrayRegisters[13] = new Register("r11", 52, 4);
            this.arrayRegisters[14] = new Register("r12", 56, 4);
            
            this.arrayRegisters[15] = new Register("pc", 60, 4);
        }
    }
    
    class ThreadInfo
    {
        long stackPointer;
        long namePointer;
        long state;
        int id;
        String name;
        long priority;
    }
    
    enum ThreadState
    {
        RUNNING(0),     // Thread is runnable or running
        SLEEPING(1),    // Thread is waiting for something to happen
        COUNTSLEEP(2),  // Sleep in counted manner
        SUSPENDED(4),   // Suspend count is non-zero
        CREATING(8),    // Thread is being created
        EXITED(16);     // Thread has exited
        
        int mask;
        
        ThreadState(int mask)
        {
            this.mask = mask;
        }
    }
}
